{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<style>div.container { width: 100% }</style>\n",
    "<img style=\"float:left;  vertical-align:text-bottom;\" height=\"65\" width=\"172\" src=\"../assets/PyViz_logo_wm_line.png\" />\n",
    "<div style=\"float:right; vertical-align:text-bottom;\"><h2>Tutorial A1. Exploration with Containers</h2></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the first two sections of this tutorial we discovered how to declare static HoloViews elements and compose them one by one into composite objects, allowing us to quickly visualize data as we explore it. However, many datasets contain numerous additional dimensions of data, such as the same measurement repeated across a large number of different settings or parameter values. To address these common situations, HoloViews provides containers that allow you to explore extra dimensions of your data using widgets, as animations, or by \"faceting\" it (splitting it into [\"small multiples\"](https://en.wikipedia.org/wiki/Small_multiple)) in various ways.\n",
    "\n",
    "<div style=\"margin: 10px\">\n",
    "<a href=\"http://holoviews.org\"><img style=\"margin:8px; display:inline; object-fit:scale-down; max-height:150px\" src=\"../assets/holoviews.png\"/></a>\n",
    "<a href=\"http://bokeh.pydata.org\"><img style=\"margin:8px; display:inline; object-fit:scale-down; max-height:150px\" src=\"../assets/bokeh.png\"/></a>\n",
    "<a href=\"http://numpy.org\"><img style=\"margin:8px; display:inline; object-fit:scale-down; max-height:150px\" src=\"../assets/numpy.png\"/></a>\n",
    "</div>\n",
    "\n",
    "To begin with we will discover how we can quickly explore the parameters of a function by having it return an element and then evaluating the function over the parameter space."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import holoviews as hv\n",
    "hv.extension('bokeh')\n",
    "%opts Curve Area [width=600]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Declaring elements in a function"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we write a function that accepts one or more parameters and constructs an element, we can build plots that do things like:\n",
    "\n",
    "* Loading data from disk as needed\n",
    "* Querying data from an API\n",
    "* Calculating data from a mathematical function\n",
    "* Generating data from a simulation\n",
    "\n",
    "As a basic example, let's declare a function that generates a frequency-modulated signal and returns a [``Curve``](http://holoviews.org/reference/elements/bokeh/Curve.html) element:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def fm_modulation(f_carrier=110, f_mod=110, mod_index=1, length=0.1, sampleRate=3000):\n",
    "    x = np.arange(0, length, 1.0/sampleRate)\n",
    "    y = np.sin(2*np.pi*f_carrier*x + mod_index*np.sin(2*np.pi*f_mod*x))\n",
    "    return hv.Curve((x, y), kdims=['Time'], vdims=['Amplitude'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The function defines a number of parameters that will change the signal, but using the default parameters the function outputs a ``Curve`` like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fm_modulation()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## HoloMaps\n",
    "\n",
    "The ``HoloMap`` is the first container type we will start working with, because it is often the starting point of a parameter exploration. HoloMaps allow exploring a parameter space sampled at specific, discrete values, and can easily be created using a dictionary comprehension. When declaring a [``HoloMap``](http://holoviews.org/reference/containers/bokeh/HoloMap.html), just ensure the length and ordering of the key tuple matches the key dimensions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "carrier_frequencies = [10, 20, 110, 220, 330]\n",
    "modulation_frequencies = [110, 220, 330]\n",
    "\n",
    "hmap = hv.HoloMap({(fc, fm): fm_modulation(fc, fm) for fc in carrier_frequencies\n",
    "                   for fm in modulation_frequencies}, kdims=['fc', 'fm'])\n",
    "hmap"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note how the keys in our ``HoloMap`` map on to two automatically generated sliders. HoloViews can generate two types of widgets: sliders for numeric values, or a dropdown selection menu for all other types. These sliders appear because a HoloMap can display only a single Element at one time, and the user must thus select which of the available elements to show at any one time."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Exercise: Try changing the function below to return an ``Area`` or ``Scatter`` element,\n",
    "# in the same way `fm_modulation` returned a ``Curve`` element.\n",
    "def fm_modulation2(f_carrier=220, f_mod=110, mod_index=1, length=0.1, sampleRate=3000):\n",
    "    x = np.arange(0,length, 1.0/sampleRate)\n",
    "    y = np.sin(2*np.pi*f_carrier*x + mod_index*np.sin(2*np.pi*f_mod*x))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Then declare a HoloMap like above and assign it to a ``exercise_hmap`` variable and display that\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Apart from their simplicity and generality, one of the key features of HoloMaps is that they can be exported to a static HTML file, GIF, or video, because every combination of the sliders (parameter values) has been pre-computed already.  Of course, this very convenient feature of pre-computation becomes a liability for very large or densely sampled parameter spaces, leading to the DynamicMap type discussed next.\n",
    "\n",
    "\n",
    "#### Summary\n",
    "\n",
    "* HoloMaps allow declaring a parameter space\n",
    "* The default widgets provide a slider for numeric types and a dropdown menu for non-numeric types.\n",
    "* HoloMap works well for small or sparsely sampled parameter spaces, exporting to static files"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## DynamicMap\n",
    "\n",
    "A [``DynamicMap``]((holoviews.org/reference/containers/bokeh/DynamicMap.html) is very similar to a ``HoloMap`` except that it evaluates the function lazily. This property makes DynamicMap require a live, running Python server, not just an HTML-serving web site or email, and it may be slow if each frame is slower to compute than it is to display.  However, because of these properties, DynamicMap allows exploring arbitrarily large parameter spaces, dynamically generating each element as needed to satisfy a request from the user. The key dimensions ``kdims`` must match the arguments of the function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Curve (color='red')\n",
    "dmap = hv.DynamicMap(fm_modulation, kdims=['f_carrier', 'f_mod', 'mod_index'])\n",
    "dmap = dmap.redim.range(f_carrier=((10, 110)), f_mod=(10, 110), mod_index=(0.1, 2))\n",
    "dmap"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Exercise: Declare a DynamicMap using the function from the previous exercise and name it ``exercise_dmap``\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Exercise (Optional): Use the ``.redim.step`` method and a floating point range to modify the slider step\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Faceting parameter spaces"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Casting"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "HoloMaps and DynamicMaps let you explore a multidimensional parameter space by looking at one point in that space at a time, which is often but not always sufficient. If you want to see more data at once, you can facet the HoloMap to put some data points side by side or overlaid to facilitate comparison.  One easy way to do that is to cast your HoloMap into a [``GridSpace``](http://holoviews.org/reference/elements/bokeh/GridSpace.html), [``NdLayout``](http://holoviews.org/reference/elements/bokeh/NdLayout.html), or [``NdOverlay``](http://holoviews.org/reference/elements/bokeh/NdOverlay.html) container:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Curve [width=150]\n",
    "hv.GridSpace(hmap).opts()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Exercise: Try casting your ``exercise_hmap`` HoloMap from the first exercise to an ``NdLayout`` or \n",
    "# ``NdOverlay``, guessing from the name what the resulting organization will be before testing it.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Faceting with methods"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using the ``.overlay``, ``.grid`` and ``.layout`` methods we can facet multi-dimensional data by a specific dimension:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hmap.overlay('fm')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using these methods with a DynamicMap requires special attention, because a dynamic map can return an infinite number of different values along its dimensions, unlike a HoloMap. Obviously, HoloViews could not comply with such a request, but these methods are perfectly legal with ``DynamicMap`` if you also define which specific dimension ``values`` you need, using the ``.redim.values`` method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Curve [width=150]\n",
    "dmap.redim.values(f_mod=[10, 20, 30], f_carrier=[10, 20, 30]).overlay('f_mod').grid('f_carrier').opts()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Exercise: Facet the ``exercise_dmap`` DynamicMap using ``.overlay`` and ``.grid``\n",
    "# Hint: Use the .redim.values method to set discrete values for ``f_mod`` and ``f_carrier`` dimensions\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Optional\n",
    "\n",
    "### Slicing and indexing"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "HoloMaps and other containers also allow you to easily index or select by key, allowing you to:\n",
    "\n",
    "* select a specific key: ``obj[10, 110]``\n",
    "* select a slice: ``obj[10, 200:]``\n",
    "* select multiple values: ``obj[[10, 110], 110]``"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Curve [width=300]\n",
    "hmap[10, 110] + hmap[10, 200:].overlay() + hmap[[10, 110], 110].overlay()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can do the same using the select method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "(hmap.select(fc=10, fm=110) +\n",
    " hmap.select(fc=10, fm=(200, None)).overlay() +\n",
    " hmap.select(fc=[10, 110], fm=110).overlay())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Exercise: Try selecting two carrier frequencies and two modulation frequencies on the ``exercise_hmap``\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Onwards\n",
    "\n",
    "* Learn more about using HoloMaps and other containers in the [Dimensioned Containers](http://holoviews.org/user_guide/Dimensioned_Containers.html) user guide\n",
    "* Learn more about working with DynamicMap in the [Live Data](http://holoviews.org/user_guide/Live_Data.html) user guide.\n",
    "\n",
    "The following section will talk about building containers from data stored in tabular (spreadsheet-like) formats, which is a very common situation given special support."
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
